:art: Basic Realize

huangqimin001 3 years ago
parent
commit
c6838a7aad

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
1 1
 *.db
2
+*.rdb
2 3
 *.swp
3 4
 *.idea
4 5
 *.sqlite3

+ 0 - 0
account/__init__.py


+ 33 - 0
account/admin.py

@@ -0,0 +1,33 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.conf import settings
4
+from django.contrib import admin
5
+from django.contrib.auth.hashers import make_password
6
+from pysnippets.strsnippets import strip
7
+
8
+from account.models import AdministratorInfo
9
+from equipment.models import IsolationPointInfo
10
+
11
+
12
+class AdministratorInfoAdmin(admin.ModelAdmin):
13
+    list_display = ('admin_id', 'phone', 'password', 'encryption', 'name', 'point_id', 'point_name', 'user_status', 'status', 'created_at', 'updated_at')
14
+    list_filter = ('user_status', 'status', 'point_name')
15
+    readonly_fields = ('encryption', 'point_name')
16
+
17
+    def save_model(self, request, obj, form, change):
18
+        obj.phone = strip(obj.phone)
19
+        obj.password = strip(obj.password)
20
+        if obj.password:
21
+            obj.encryption = make_password(obj.password, settings.MAKE_PASSWORD_SALT, settings.MAKE_PASSWORD_HASHER)
22
+        obj.password = ''
23
+
24
+        obj.point_id = strip(obj.point_id)
25
+        try:
26
+            obj.point_name = IsolationPointInfo.objects.get(point_id=obj.point_id).point_name
27
+        except IsolationPointInfo.DoesNotExist:
28
+            obj.point_name = ''
29
+
30
+        obj.save()
31
+
32
+
33
+admin.site.register(AdministratorInfo, AdministratorInfoAdmin)

+ 5 - 0
account/apps.py

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class AccountConfig(AppConfig):
5
+    name = 'account'

+ 36 - 0
account/migrations/0001_initial.py

@@ -0,0 +1,36 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 01:41
2
+
3
+from django.db import migrations, models
4
+import shortuuidfield.fields
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    initial = True
10
+
11
+    dependencies = [
12
+    ]
13
+
14
+    operations = [
15
+        migrations.CreateModel(
16
+            name='AdministratorInfo',
17
+            fields=[
18
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')),
20
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
21
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
22
+                ('admin_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='管理员唯一标识', max_length=22, null=True, unique=True)),
23
+                ('phone', models.CharField(blank=True, db_index=True, help_text='管理员电话', max_length=11, null=True, verbose_name='phone')),
24
+                ('password', models.CharField(blank=True, help_text='管理员密码', max_length=255, null=True, verbose_name='password')),
25
+                ('encryption', models.CharField(blank=True, help_text='管理员密码', max_length=255, null=True, verbose_name='encryption')),
26
+                ('name', models.CharField(blank=True, help_text='管理员姓名', max_length=255, null=True, verbose_name='name')),
27
+                ('point_id', models.CharField(blank=True, db_index=True, help_text='隔离点唯一标识', max_length=32, null=True, verbose_name='point_id')),
28
+                ('point_name', models.CharField(blank=True, help_text='隔离点名称', max_length=255, null=True, verbose_name='point_name')),
29
+                ('user_status', models.IntegerField(choices=[(1, '已激活'), (2, '已禁用'), (3, '已删除')], db_index=True, default=1, help_text='管理员状态', verbose_name='user_status')),
30
+            ],
31
+            options={
32
+                'verbose_name': '管理员信息',
33
+                'verbose_name_plural': '管理员信息',
34
+            },
35
+        ),
36
+    ]

+ 0 - 0
account/migrations/__init__.py


+ 49 - 0
account/models.py

@@ -0,0 +1,49 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import ugettext_lazy as _
5
+from django_models_ext import BaseModelMixin
6
+from shortuuidfield import ShortUUIDField
7
+
8
+from equipment.models import IsolationPointInfo
9
+
10
+
11
+class AdministratorInfo(BaseModelMixin):
12
+    ACTIVATED = 1
13
+    DISABLED = 2
14
+    DELETED = 3
15
+
16
+    USER_STATUS_TUPLE = (
17
+        (ACTIVATED, '已激活'),
18
+        (DISABLED, '已禁用'),
19
+        (DELETED, '已删除'),
20
+    )
21
+
22
+    admin_id = ShortUUIDField(_('admin_id'), max_length=32, blank=True, null=True, help_text='管理员唯一标识', db_index=True, unique=True)
23
+
24
+    phone = models.CharField(_('phone'), max_length=11, blank=True, null=True, help_text='管理员电话', db_index=True)
25
+    password = models.CharField(_('password'), max_length=255, blank=True, null=True, help_text='管理员密码')
26
+    encryption = models.CharField(_('encryption'), max_length=255, blank=True, null=True, help_text='管理员密码')
27
+
28
+    name = models.CharField(_('name'), max_length=255, blank=True, null=True, help_text='管理员姓名')
29
+
30
+    point_id = models.CharField(_('point_id'), max_length=32, blank=True, null=True, help_text='隔离点唯一标识', db_index=True)
31
+    point_name = models.CharField(_('point_name'), max_length=255, blank=True, null=True, help_text='隔离点名称')
32
+
33
+    user_status = models.IntegerField(_('user_status'), choices=USER_STATUS_TUPLE, default=ACTIVATED, help_text='管理员状态', db_index=True)
34
+
35
+    class Meta:
36
+        verbose_name = _('管理员信息')
37
+        verbose_name_plural = _('管理员信息')
38
+
39
+    def __unicode__(self):
40
+        return '{}-{}'.format(self.name, self.phone)
41
+
42
+    @property
43
+    def data(self):
44
+        return {
45
+            'admin_id': self.admin_id,
46
+            'name': self.name,
47
+            'point_id': self.point_id,
48
+            'point_name': self.point_name,
49
+        }

+ 4 - 0
account/tests.py

@@ -0,0 +1,4 @@
1
+from django.test import TestCase
2
+
3
+
4
+# Create your tests here.

+ 4 - 0
account/views.py

@@ -0,0 +1,4 @@
1
+from django.shortcuts import render
2
+
3
+
4
+# Create your views here.

+ 41 - 0
api/admin_views.py

@@ -0,0 +1,41 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+from django.conf import settings
6
+from django.contrib.auth.hashers import check_password
7
+from django_logit import logit
8
+from django_response import response
9
+
10
+from account.models import AdministratorInfo
11
+from equipment.models import IsolationPointInfo
12
+from utils.error.errno_utils import AdministratorStatusCode, IsolationPointStatusCode
13
+
14
+
15
+WECHAT = settings.WECHAT
16
+
17
+
18
+@logit
19
+def admin_login(request):
20
+    phone = request.POST.get('phone', '')
21
+    password = request.POST.get('password', '')
22
+
23
+    try:
24
+        administrator = AdministratorInfo.objects.get(phone=phone, status=True)
25
+    except AdministratorInfo.DoesNotExist:
26
+        return response(AdministratorStatusCode.ADMINISTRATOR_NOT_FOUND)
27
+
28
+    if administrator.user_status == AdministratorInfo.DISABLED:
29
+        return response(AdministratorStatusCode.ADMINISTRATOR_NOT_ACTIVATED)
30
+    elif administrator.user_status == AdministratorInfo.DELETED:
31
+        return response(AdministratorStatusCode.ADMINISTRATOR_HAS_DELETED)
32
+
33
+    if not check_password(password, administrator.encryption):
34
+        return response(AdministratorStatusCode.ADMINISTRATOR_PASSWORD_ERROR)
35
+
36
+    try:
37
+        point = IsolationPointInfo.objects.get(point_id=administrator.point_id, status=True)
38
+    except IsolationPointInfo.DoesNotExist:
39
+        return response(IsolationPointStatusCode.ISOLATIONPOINT_NOT_FOUND)
40
+
41
+    return response(200, 'Admin Login Success', '管理员登录成功', data={**administrator.data, **point.data})

+ 121 - 0
api/eqpt_views.py

@@ -0,0 +1,121 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+from django.db.models import Q
6
+from django_logit import logit
7
+from django_response import response
8
+from TimeConvert import TimeConvert as tc
9
+
10
+from equipment.models import (IsolationPointInfo, ThermometerEquipmentInfo, ThermometerMeasureInfo,
11
+                              ThermometerMeasureLogInfo)
12
+from utils.error.errno_utils import IsolationPointStatusCode, ThermometerEquipmentStatusCode
13
+
14
+
15
+@logit
16
+def eqpt_bind(request):
17
+    point_id = request.POST.get('point_id', '')
18
+    macid = request.POST.get('macid', '')
19
+    sn = request.POST.get('sn', '')
20
+
21
+    ThermometerEquipmentInfo.objects.update_or_create(macid=macid, defaults={
22
+        'point_id': point_id,
23
+        'sn': sn,
24
+    })
25
+
26
+    return response()
27
+
28
+
29
+@logit
30
+def eqpt_onoff(request):
31
+    macid = request.POST.get('macid', '')
32
+    active = request.POST.get('active', 0)
33
+
34
+    ThermometerEquipmentInfo.objects.update_or_create(macid=macid, defaults={
35
+        'active_status': active,
36
+        'active_at': tc.utc_datetime(),
37
+    })
38
+
39
+    return response()
40
+
41
+
42
+@logit
43
+def eqpt_list(request):
44
+    point_id = request.POST.get('point_id', '')
45
+    macid = request.POST.get('macid', '')
46
+
47
+    eqpts = ThermometerEquipmentInfo.objects.filter(point_id=point_id, status=True)
48
+    if macid:
49
+        eqpts = eqpts.filter(macid=macid)
50
+    eqpts = [eqpt.data for eqpt in eqpts]
51
+
52
+    total_num = len(eqpts)
53
+    active_num = len([1 for eqpt in eqpts if eqpt.get('active_status') == ThermometerEquipmentInfo.ONLINE])
54
+
55
+    return response(data={
56
+        'eqpts': eqpts,
57
+        'total_num': total_num,
58
+        'active_num': active_num,
59
+        'unactive_num': total_num - active_num,
60
+    })
61
+
62
+
63
+@logit
64
+def eqpt_result(request):
65
+    point_id = request.POST.get('point_id', '')
66
+    kw = request.POST.get('kw', '')
67
+
68
+    try:
69
+        point = IsolationPointInfo.objects.get(point_id=point_id, status=True)
70
+    except IsolationPointInfo.DoesNotExist:
71
+        return response(IsolationPointStatusCode.ISOLATIONPOINT_NOT_FOUND)
72
+
73
+    logs = ThermometerMeasureInfo.objects.filter(point_id=point_id, point_measure_ymd=tc.local_string(format='%Y-%m-%d'), point_measure_window=point.point_measure_window, status=True).order_by('-pk')
74
+
75
+    eqpts = ThermometerEquipmentInfo.objects.filter(point_id=point_id, active_status=ThermometerEquipmentInfo.ONLINE, status=True)
76
+    if kw:
77
+        eqpts = eqpts.filter(Q(name__icontains=kw) | Q(phone__icontains=kw))
78
+    eqpts = [eqpt.data for eqpt in eqpts]
79
+
80
+    return response(data={
81
+        'eqpts': eqpts,
82
+    })
83
+
84
+
85
+@logit
86
+def upload_temperature(request):
87
+    macid = request.POST.get('macid', '')
88
+    name = request.POST.get('name', '')
89
+    sex = request.POST.get('sex', 0)
90
+    age = request.POST.get('age', 0)
91
+    phone = request.POST.get('phone', '')
92
+    temperature = request.POST.get('temperature', 0)
93
+
94
+    try:
95
+        eqpt = ThermometerEquipmentInfo.objects.get(macid=macid, status=True)
96
+    except ThermometerEquipmentInfo.DoesNotExist:
97
+        return response(ThermometerEquipmentStatusCode.THERMOMETER_EQUIPMENT_NOT_FOUND)
98
+
99
+    try:
100
+        point = IsolationPointInfo.objects.get(point_id=eqpt.point_id, status=True)
101
+    except IsolationPointInfo.DoesNotExist:
102
+        return response(IsolationPointStatusCode.ISOLATIONPOINT_NOT_FOUND)
103
+
104
+    point_measure_ymd = tc.local_string(format='%Y-%m-%d')
105
+    point_measure_window = point.current_measure_window
106
+
107
+    eqpt.name = name
108
+    eqpt.sex = sex
109
+    eqpt.age = age
110
+    eqpt.phone = phone
111
+    eqpt.last_submit_at = tc.utc_datetime()
112
+    eqpt.save()
113
+
114
+    ThermometerMeasureLogInfo.objects.create(point_id=eqpt.point_id, macid=macid, temperature=temperature)
115
+
116
+    if point_measure_window:
117
+        ThermometerMeasureInfo.objects.update_or_create(point_id=eqpt.point_id, point_measure_ymd=point_measure_ymd, point_measure_window=point_measure_window, macid=macid, defaults={
118
+            'temperature': temperature,
119
+        })
120
+
121
+    return response()

+ 25 - 0
api/point_views.py

@@ -0,0 +1,25 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+from django_logit import logit
6
+from django_response import response
7
+
8
+from equipment.models import IsolationPointInfo
9
+from utils.error.errno_utils import IsolationPointStatusCode
10
+
11
+
12
+@logit
13
+def measure_window(request):
14
+    point_id = request.POST.get('point_id', '')
15
+    point_measure_window = request.POST.get('point_measure_window', '')
16
+
17
+    try:
18
+        point = IsolationPointInfo.objects.get(point_id=point_id, status=True)
19
+    except IsolationPointInfo.DoesNotExist:
20
+        return response(IsolationPointStatusCode.ISOLATIONPOINT_NOT_FOUND)
21
+
22
+    point.point_measure_window = point_measure_window
23
+    point.save()
24
+
25
+    return response()

+ 20 - 1
api/urls.py

@@ -2,7 +2,7 @@
2 2
 
3 3
 from django.conf.urls import url
4 4
 
5
-from api import oauth_views
5
+from api import admin_views, eqpt_views, oauth_views, point_views
6 6
 
7 7
 
8 8
 urlpatterns = [
@@ -12,3 +12,22 @@ urlpatterns += [
12 12
     url(r'^3rd/or$', oauth_views.oauth_redirect, name='3rd_or'),
13 13
     url(r'^3rd/oauth_redirect$', oauth_views.oauth_redirect, name='3rd_oauth_redirect'),
14 14
 ]
15
+
16
+urlpatterns += [
17
+    url(r'^admin/login$', admin_views.admin_login, name='admin_login'),
18
+]
19
+
20
+urlpatterns += [
21
+    url(r'^point/measure_window$', point_views.measure_window, name='measure_window'),
22
+]
23
+
24
+urlpatterns += [
25
+    url(r'^eqpt/bind$', eqpt_views.eqpt_bind, name='eqpt_bind'),
26
+    url(r'^eqpt/onoff$', eqpt_views.eqpt_onoff, name='eqpt_onoff'),
27
+    url(r'^eqpt/list$', eqpt_views.eqpt_list, name='eqpt_list'),
28
+    url(r'^eqpt/result$', eqpt_views.eqpt_result, name='eqpt_result'),
29
+]
30
+
31
+urlpatterns += [
32
+    url(r'^upload/temperature$', eqpt_views.upload_temperature, name='upload_temperature'),
33
+]

+ 0 - 0
equipment/__init__.py


+ 31 - 0
equipment/admin.py

@@ -0,0 +1,31 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.contrib import admin
4
+
5
+from equipment.models import (IsolationPointInfo, ThermometerEquipmentInfo, ThermometerMeasureInfo,
6
+                              ThermometerMeasureLogInfo)
7
+
8
+
9
+class IsolationPointInfoAdmin(admin.ModelAdmin):
10
+    list_display = ('point_id', 'point_name', 'point_measure_window', 'status', 'updated_at', 'created_at')
11
+
12
+
13
+class ThermometerEquipmentInfoAdmin(admin.ModelAdmin):
14
+    list_display = ('eqpt_id', 'point_id', 'macid', 'sn', 'active_status', 'active_at', 'name', 'sex', 'age', 'phone', 'remark', 'last_submit_at', 'status', 'updated_at', 'created_at')
15
+    list_filter = ('point_id', 'status')
16
+
17
+
18
+class ThermometerMeasureInfoAdmin(admin.ModelAdmin):
19
+    list_display = ('point_id', 'point_measure_ymd', 'point_measure_window', 'macid', 'sn', 'temperature', 'status', 'updated_at', 'created_at')
20
+    list_filter = ('point_id', 'status')
21
+
22
+
23
+class ThermometerMeasureLogInfoAdmin(admin.ModelAdmin):
24
+    list_display = ('point_id', 'macid', 'sn', 'temperature', 'status', 'updated_at', 'created_at')
25
+    list_filter = ('point_id', 'status')
26
+
27
+
28
+admin.site.register(IsolationPointInfo, IsolationPointInfoAdmin)
29
+admin.site.register(ThermometerEquipmentInfo, ThermometerEquipmentInfoAdmin)
30
+admin.site.register(ThermometerMeasureInfo, ThermometerMeasureInfoAdmin)
31
+admin.site.register(ThermometerMeasureLogInfo, ThermometerMeasureLogInfoAdmin)

+ 5 - 0
equipment/apps.py

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class AccountConfig(AppConfig):
5
+    name = 'equipment'

+ 72 - 0
equipment/migrations/0001_initial.py

@@ -0,0 +1,72 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 01:41
2
+
3
+from django.db import migrations, models
4
+import jsonfield.fields
5
+import shortuuidfield.fields
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    initial = True
11
+
12
+    dependencies = [
13
+    ]
14
+
15
+    operations = [
16
+        migrations.CreateModel(
17
+            name='IsolationPointInfo',
18
+            fields=[
19
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')),
21
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
22
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
23
+                ('point_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='隔离点唯一标识', max_length=22, null=True, unique=True)),
24
+                ('point_name', models.CharField(blank=True, help_text='隔离点名称', max_length=255, null=True, verbose_name='point_name')),
25
+                ('point_measure_window', jsonfield.fields.JSONField(blank=True, default=[], help_text='隔离点测温时间段', null=True, verbose_name='point_measure_window')),
26
+            ],
27
+            options={
28
+                'verbose_name': '隔离点信息',
29
+                'verbose_name_plural': '隔离点信息',
30
+            },
31
+        ),
32
+        migrations.CreateModel(
33
+            name='ThermometerEquipmentInfo',
34
+            fields=[
35
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
36
+                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')),
37
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
38
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
39
+                ('eqpt_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='设备唯一标识', max_length=22, null=True, unique=True)),
40
+                ('point_id', models.CharField(blank=True, db_index=True, help_text='隔离点唯一标识', max_length=32, null=True, verbose_name='point_id')),
41
+                ('macid', models.CharField(blank=True, help_text='设备号', max_length=255, null=True, verbose_name='macid')),
42
+                ('sn', models.CharField(blank=True, help_text='序列号', max_length=255, null=True, verbose_name='sn')),
43
+                ('active_status', models.IntegerField(choices=[(1, '已激活'), (0, '已离线')], default=0, help_text='激活状态', verbose_name='active_status')),
44
+                ('active_at', models.DateTimeField(blank=True, help_text='激活时间', null=True, verbose_name='active_at')),
45
+                ('name', models.CharField(blank=True, help_text='用户姓名', max_length=255, null=True, verbose_name='name')),
46
+                ('sex', models.IntegerField(choices=[(0, '未知'), (1, '男'), (2, '女')], default=0, help_text='用户性别', verbose_name='sex')),
47
+                ('age', models.IntegerField(default=0, help_text='用户年龄', verbose_name='age')),
48
+                ('phone', models.CharField(blank=True, db_index=True, help_text='用户电话', max_length=11, null=True, verbose_name='phone')),
49
+                ('remark', models.CharField(blank=True, help_text='备注', max_length=255, null=True, verbose_name='remark')),
50
+            ],
51
+            options={
52
+                'verbose_name': '测温设备信息',
53
+                'verbose_name_plural': '测温设备信息',
54
+            },
55
+        ),
56
+        migrations.CreateModel(
57
+            name='ThermometerMeasureInfo',
58
+            fields=[
59
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
60
+                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')),
61
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
62
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
63
+                ('macid', models.CharField(blank=True, help_text='设备号', max_length=255, null=True, verbose_name='macid')),
64
+                ('sn', models.CharField(blank=True, help_text='序列号', max_length=255, null=True, verbose_name='sn')),
65
+                ('temperature', models.FloatField(default=0, help_text='用户体温', verbose_name='temperature')),
66
+            ],
67
+            options={
68
+                'verbose_name': '测温记录信息',
69
+                'verbose_name_plural': '测温记录信息',
70
+            },
71
+        ),
72
+    ]

+ 18 - 0
equipment/migrations/0002_thermometermeasureinfo_point_id.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 04:54
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('equipment', '0001_initial'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='thermometermeasureinfo',
15
+            name='point_id',
16
+            field=models.CharField(blank=True, db_index=True, help_text='隔离点唯一标识', max_length=32, null=True, verbose_name='point_id'),
17
+        ),
18
+    ]

+ 43 - 0
equipment/migrations/0003_auto_20210709_1332.py

@@ -0,0 +1,43 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 05:32
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('equipment', '0002_thermometermeasureinfo_point_id'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='ThermometerMeasureLogInfo',
15
+            fields=[
16
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17
+                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')),
18
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
19
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
20
+                ('point_id', models.CharField(blank=True, db_index=True, help_text='隔离点唯一标识', max_length=32, null=True, verbose_name='point_id')),
21
+                ('macid', models.CharField(blank=True, help_text='设备号', max_length=255, null=True, verbose_name='macid')),
22
+                ('sn', models.CharField(blank=True, help_text='序列号', max_length=255, null=True, verbose_name='sn')),
23
+                ('temperature', models.FloatField(default=0, help_text='用户体温', verbose_name='temperature')),
24
+            ],
25
+            options={
26
+                'verbose_name': '测温记录信息',
27
+                'verbose_name_plural': '测温记录信息',
28
+            },
29
+        ),
30
+        migrations.AlterModelOptions(
31
+            name='thermometermeasureinfo',
32
+            options={'verbose_name': '测温信息', 'verbose_name_plural': '测温信息'},
33
+        ),
34
+        migrations.AddField(
35
+            model_name='thermometermeasureinfo',
36
+            name='point_measure_window',
37
+            field=models.CharField(blank=True, db_index=True, help_text='隔离点测温时间段', max_length=16, null=True, verbose_name='point_measure_window'),
38
+        ),
39
+        migrations.AlterUniqueTogether(
40
+            name='thermometermeasureinfo',
41
+            unique_together={('point_id', 'point_measure_window')},
42
+        ),
43
+    ]

+ 17 - 0
equipment/migrations/0004_alter_thermometermeasureinfo_unique_together.py

@@ -0,0 +1,17 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 05:34
2
+
3
+from django.db import migrations
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('equipment', '0003_auto_20210709_1332'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterUniqueTogether(
14
+            name='thermometermeasureinfo',
15
+            unique_together={('point_id', 'point_measure_window', 'macid')},
16
+        ),
17
+    ]

+ 18 - 0
equipment/migrations/0005_thermometerequipmentinfo_last_submit_at.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 05:42
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('equipment', '0004_alter_thermometermeasureinfo_unique_together'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='thermometerequipmentinfo',
15
+            name='last_submit_at',
16
+            field=models.DateTimeField(blank=True, help_text='上一次上报时间', null=True, verbose_name='last_submit_at'),
17
+        ),
18
+    ]

+ 22 - 0
equipment/migrations/0006_auto_20210709_1346.py

@@ -0,0 +1,22 @@
1
+# Generated by Django 3.2.4 on 2021-07-09 05:46
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('equipment', '0005_thermometerequipmentinfo_last_submit_at'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='thermometermeasureinfo',
15
+            name='point_measure_ymd',
16
+            field=models.CharField(blank=True, db_index=True, help_text='隔离点测温日期', max_length=10, null=True, verbose_name='point_measure_ymd'),
17
+        ),
18
+        migrations.AlterUniqueTogether(
19
+            name='thermometermeasureinfo',
20
+            unique_together={('point_id', 'point_measure_ymd', 'point_measure_window', 'macid')},
21
+        ),
22
+    ]

+ 0 - 0
equipment/migrations/__init__.py


+ 157 - 0
equipment/models.py

@@ -0,0 +1,157 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import ugettext_lazy as _
5
+from django_models_ext import BaseModelMixin, SexModelMixin
6
+from jsonfield import JSONField
7
+from shortuuidfield import ShortUUIDField
8
+from TimeConvert import TimeConvert as tc
9
+
10
+
11
+class IsolationPointInfo(BaseModelMixin):
12
+    point_id = ShortUUIDField(_('point_id'), max_length=32, blank=True, null=True, help_text='隔离点唯一标识', db_index=True, unique=True)
13
+    point_name = models.CharField(_('point_name'), max_length=255, blank=True, null=True, help_text='隔离点名称')
14
+    # [{"start": "8:00", "end": "9:00"}, {"start": "12:00", "end": "14:00"}]
15
+    point_measure_window = JSONField(_('point_measure_window'), default=[], blank=True, null=True, help_text='隔离点测温时间段')
16
+
17
+    class Meta:
18
+        verbose_name = _('隔离点信息')
19
+        verbose_name_plural = _('隔离点信息')
20
+
21
+    def __unicode__(self):
22
+        return '%d' % self.pk
23
+
24
+    @property
25
+    def data(self):
26
+        return {
27
+            'point_id': self.point_id,
28
+            'point_name': self.point_name,
29
+            'point_measure_window': self.point_measure_window,
30
+        }
31
+
32
+    @property
33
+    def current_measure_window(self):
34
+        current_ymd = tc.local_string(format='%Y-%m-%d')
35
+        current_dt = tc.utc_datetime()
36
+        for window in self.point_measure_window:
37
+            start_t, end_t = window.get('start'), window.get('end')
38
+            start_dt = tc.string_to_utc_datetime(f'{current_ymd} {start_t}:00')
39
+            end_dt = tc.string_to_utc_datetime(f'{current_ymd} {end_t}:00')
40
+            if start_dt < current_dt < end_dt:
41
+                return f'{start_t}-{end_t}'
42
+        return ''
43
+
44
+
45
+class ThermometerEquipmentInfo(BaseModelMixin):
46
+    ONLINE = 1
47
+    OFFLINE = 0
48
+
49
+    ACTIVE_STATUE_TUPLE = (
50
+        (ONLINE, '已激活'),
51
+        (OFFLINE, '已离线'),
52
+    )
53
+
54
+    eqpt_id = ShortUUIDField(_('eqpt_id'), max_length=32, blank=True, null=True, help_text='设备唯一标识', db_index=True, unique=True)
55
+
56
+    point_id = models.CharField(_('point_id'), max_length=32, blank=True, null=True, help_text='隔离点唯一标识', db_index=True)
57
+
58
+    macid = models.CharField(_('macid'), max_length=255, blank=True, null=True, help_text='设备号')
59
+    sn = models.CharField(_('sn'), max_length=255, blank=True, null=True, help_text='序列号')
60
+
61
+    active_status = models.IntegerField(_('active_status'), choices=ACTIVE_STATUE_TUPLE, default=OFFLINE, help_text='激活状态')
62
+    active_at = models.DateTimeField(_('active_at'), blank=True, null=True, help_text=_('激活时间'))
63
+
64
+    # 用户基本信息
65
+    name = models.CharField(_('name'), max_length=255, blank=True, null=True, help_text='用户姓名')
66
+    sex = models.IntegerField(_('sex'), choices=SexModelMixin.SEX_TUPLE, default=SexModelMixin.UNKNOWN, help_text='用户性别')
67
+    age = models.IntegerField(_('age'), default=0, help_text='用户年龄')
68
+    phone = models.CharField(_('phone'), max_length=11, blank=True, null=True, help_text='用户电话', db_index=True)
69
+
70
+    remark = models.CharField(_('remark'), max_length=255, blank=True, null=True, help_text='备注')
71
+
72
+    last_submit_at = models.DateTimeField(_('last_submit_at'), blank=True, null=True, help_text=_('上一次上报时间'))
73
+
74
+    class Meta:
75
+        verbose_name = _('测温设备信息')
76
+        verbose_name_plural = _('测温设备信息')
77
+
78
+    def __unicode__(self):
79
+        return '%d' % self.pk
80
+
81
+    @property
82
+    def data(self):
83
+        return {
84
+            'eqpt_id': self.eqpt_id,
85
+            'point_id': self.point_id,
86
+            'macid': self.macid,
87
+            'sn': self.sn,
88
+            'active_status': self.active_status,
89
+            'active_status_str': dict(self.ACTIVE_STATUE_TUPLE).get(self.active_status, ''),
90
+            'active_at': tc.local_string(utc_dt=self.active_at),
91
+            'name': self.name or '',
92
+            'sex': self.sex,
93
+            'sex_str': dict(SexModelMixin.SEX_TUPLE).get(self.sex, ''),
94
+            'age': self.age or '',
95
+            'phone': self.phone or '',
96
+            'remark': self.remark or '',
97
+            'last_submit_at': tc.local_string(utc_dt=self.last_submit_at),
98
+            'created_at': tc.local_string(utc_dt=self.created_at),
99
+        }
100
+
101
+
102
+class ThermometerMeasureInfo(BaseModelMixin):
103
+    point_id = models.CharField(_('point_id'), max_length=32, blank=True, null=True, help_text='隔离点唯一标识', db_index=True)
104
+
105
+    point_measure_ymd = models.CharField(_('point_measure_ymd'), max_length=10, blank=True, null=True, help_text='隔离点测温日期', db_index=True)
106
+    point_measure_window = models.CharField(_('point_measure_window'), max_length=16, blank=True, null=True, help_text='隔离点测温时间段', db_index=True)
107
+
108
+    macid = models.CharField(_('macid'), max_length=255, blank=True, null=True, help_text='设备号')
109
+    sn = models.CharField(_('sn'), max_length=255, blank=True, null=True, help_text='序列号')
110
+
111
+    temperature = models.FloatField(_('temperature'), default=0, help_text='用户体温')
112
+
113
+    class Meta:
114
+        verbose_name = _('测温信息')
115
+        verbose_name_plural = _('测温信息')
116
+
117
+        unique_together = (
118
+            ('point_id', 'point_measure_ymd', 'point_measure_window', 'macid')
119
+        )
120
+
121
+    def __unicode__(self):
122
+        return '%d' % self.pk
123
+
124
+    @property
125
+    def data(self):
126
+        return {
127
+            'point_id': self.point_id,
128
+            'point_measure_window': self.point_measure_window,
129
+            'macid': self.macid,
130
+            'sn': self.sn,
131
+            'temperature': self.temperature,
132
+        }
133
+
134
+
135
+class ThermometerMeasureLogInfo(BaseModelMixin):
136
+    point_id = models.CharField(_('point_id'), max_length=32, blank=True, null=True, help_text='隔离点唯一标识', db_index=True)
137
+
138
+    macid = models.CharField(_('macid'), max_length=255, blank=True, null=True, help_text='设备号')
139
+    sn = models.CharField(_('sn'), max_length=255, blank=True, null=True, help_text='序列号')
140
+
141
+    temperature = models.FloatField(_('temperature'), default=0, help_text='用户体温')
142
+
143
+    class Meta:
144
+        verbose_name = _('测温记录信息')
145
+        verbose_name_plural = _('测温记录信息')
146
+
147
+    def __unicode__(self):
148
+        return '%d' % self.pk
149
+
150
+    @property
151
+    def data(self):
152
+        return {
153
+            'point_id': self.point_id,
154
+            'macid': self.macid,
155
+            'sn': self.sn,
156
+            'temperature': self.temperature,
157
+        }

+ 4 - 0
equipment/tests.py

@@ -0,0 +1,4 @@
1
+from django.test import TestCase
2
+
3
+
4
+# Create your tests here.

+ 4 - 0
equipment/views.py

@@ -0,0 +1,4 @@
1
+from django.shortcuts import render
2
+
3
+
4
+# Create your views here.

+ 9 - 7
thermometer/basemodels.py

@@ -5,21 +5,23 @@ from django.utils.translation import ugettext_lazy as _
5 5
 
6 6
 
7 7
 class BaseModelMixin(models.Model):
8
-    status = models.BooleanField(_(u'status'), default=True, help_text=_(u'状态'))
9
-    created_at = models.DateTimeField(_(u'created_at'), auto_now_add=True, editable=True, help_text=_(u'创建时间'))
10
-    updated_at = models.DateTimeField(_(u'updated_at'), auto_now=True, editable=True, help_text=_(u'更新时间'))
8
+    status = models.BooleanField(_('status'), default=True, help_text=_('状态'), db_index=True)
9
+    created_at = models.DateTimeField(_('created_at'), auto_now_add=True, editable=True, help_text=_('创建时间'))
10
+    updated_at = models.DateTimeField(_('updated_at'), auto_now=True, editable=True, help_text=_('更新时间'))
11 11
 
12 12
     class Meta:
13 13
         abstract = True
14 14
 
15 15
 
16 16
 class SexChoicesMixin(models.Model):
17
+    UNKNOWN = 0
17 18
     MALE = 1
18
-    FEMALE = 0
19
+    FEMALE = 2
19 20
 
20
-    SEX_TYPE = (
21
-        (MALE, u'男'),
22
-        (FEMALE, u'女'),
21
+    SEX_TUPLE = (
22
+        (UNKNOWN, '未知'),
23
+        (MALE, '男'),
24
+        (FEMALE, '女'),
23 25
     )
24 26
 
25 27
     class Meta:

+ 1 - 1
thermometer/local_settings_bak.py

@@ -14,4 +14,4 @@ EMAIL_HOST_USER = 'error.notify@exmail.com'
14 14
 EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
15 15
 DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
16 16
 ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
17
-EMAIL_SUBJECT_PREFIX = u'[Thermometer] '
17
+EMAIL_SUBJECT_PREFIX = '[Thermometer] '

+ 7 - 1
thermometer/settings.py

@@ -55,6 +55,8 @@ INSTALLED_APPS = [
55 55
     'django_we',
56 56
     'commands',
57 57
     'api',
58
+    'account',
59
+    'equipment',
58 60
 ]
59 61
 
60 62
 MIDDLEWARE = [
@@ -278,7 +280,7 @@ ADMINS = []
278 280
 MANAGERS = ADMINS
279 281
 # Subject-line prefix for email messages send with django.core.mail.mail_admins
280 282
 # or ...mail_managers.  Make sure to include the trailing space.
281
-EMAIL_SUBJECT_PREFIX = u'[Thermometer] '
283
+EMAIL_SUBJECT_PREFIX = '[Thermometer] '
282 284
 
283 285
 # Django-Admin Settings
284 286
 DJANGO_ADMIN_DISABLE_DELETE_SELECTED = False
@@ -310,6 +312,10 @@ DJANGO_WE_MODEL_DISPLAY_OR_NOT = True
310 312
 DJANGO_WE_COOKIE_MAX_AGE = COOKIE_MAX_AGE
311 313
 DJANGO_WE_COOKIE_SALT = COOKIE_SALT
312 314
 
315
+# 密码设置
316
+MAKE_PASSWORD_SALT = ''
317
+MAKE_PASSWORD_HASHER = 'pbkdf2_sha256'
318
+
313 319
 # 开发调试相关配置
314 320
 if DEBUG:
315 321
     try:

+ 47 - 24
utils/error/errno_utils.py

@@ -5,32 +5,55 @@ from StatusCode import BaseStatusCode, StatusCodeField
5 5
 
6 6
 class ParamStatusCode(BaseStatusCode):
7 7
     """ 4000xx 参数相关错误码 """
8
-    PARAM_NOT_FOUND = StatusCodeField(400000, 'Param Not Found', description=u'参数不存在')
8
+    PARAM_NOT_FOUND = StatusCodeField(400000, 'Param Not Found', description='参数不存在')
9 9
 
10 10
 
11 11
 class ProfileStatusCode(BaseStatusCode):
12 12
     """ 4001xx 用户相关错误码 """
13
-    PROFILE_NOT_FOUND = StatusCodeField(400101, 'Profile Not Found', description=u'用户不存在')
13
+    PROFILE_NOT_FOUND = StatusCodeField(400101, 'Profile Not Found', description='用户不存在')
14 14
 
15 15
 
16 16
 class PhoneStatusCode(BaseStatusCode):
17 17
     """ 4002xx 手机相关错误码 """
18
-    INVALID_PHONE = StatusCodeField(400200, 'Invalid Phone', description=u'非法手机号')
19
-    PHONE_NOT_FOUND = StatusCodeField(400201, 'Phone Not Found', description=u'手机号不存在')
20
-    PHONE_ALREADY_EXISTS = StatusCodeField(400202, 'Phone Already Exists', description=u'手机号已存在')
18
+    INVALID_PHONE = StatusCodeField(400200, 'Invalid Phone', description='非法手机号')
19
+    PHONE_NOT_FOUND = StatusCodeField(400201, 'Phone Not Found', description='手机号不存在')
20
+    PHONE_ALREADY_EXISTS = StatusCodeField(400202, 'Phone Already Exists', description='手机号已存在')
21
+
22
+
23
+class AdministratorStatusCode(BaseStatusCode):
24
+    """ 操作员相关错误码 4010xx """
25
+    ADMINISTRATOR_NOT_FOUND = StatusCodeField(401001, 'Administrator Not Found', description='管理员不存在')
26
+    # 密码
27
+    ADMINISTRATOR_PASSWORD_ERROR = StatusCodeField(401002, 'Administrator Password Error', description='管理员密码错误')
28
+    # 手机号
29
+    ADMINISTRATOR_PHONE_ALREADY_EXISTS = StatusCodeField(401005, 'Administrator Phone Already Exists', description='管理员手机号已经存在')
30
+    # 状态
31
+    ADMINISTRATOR_NOT_ACTIVATED = StatusCodeField(401015, 'Administrator Not Activated', description='管理员未激活')
32
+    ADMINISTRATOR_HAS_DISABLED = StatusCodeField(401016, 'Administrator Has Disabled', description='管理员已禁用')
33
+    ADMINISTRATOR_HAS_DELETED = StatusCodeField(401017, 'Administrator Has Deleted', description='管理员已删除')
34
+
35
+
36
+class IsolationPointStatusCode(BaseStatusCode):
37
+    """ 操作员相关错误码 4020xx """
38
+    ISOLATIONPOINT_NOT_FOUND = StatusCodeField(402001, 'IsolationPoint Not Found', description='隔离点不存在')
39
+
40
+
41
+class ThermometerEquipmentStatusCode(BaseStatusCode):
42
+    """ 操作员相关错误码 4030xx """
43
+    THERMOMETER_EQUIPMENT_NOT_FOUND = StatusCodeField(403001, 'Thermometer Equipment Not Found', description='测温设备不存在')
21 44
 
22 45
 
23 46
 class OrderStatusCode(BaseStatusCode):
24 47
     """ 4040xx 订单/支付相关错误码 """
25
-    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description=u'统一下单失败')
26
-    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description=u'订单不存在')
48
+    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description='统一下单失败')
49
+    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description='订单不存在')
27 50
     # 订单支付状态
28
-    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description=u'订单未支付')
29
-    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description=u'订单支付中')
30
-    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description=u'微信支付失败')
51
+    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description='订单未支付')
52
+    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description='订单支付中')
53
+    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description='微信支付失败')
31 54
     # 通知校验状态
32
-    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description=u'签名校验失败')
33
-    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description=u'金额校验失败')
55
+    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description='签名校验失败')
56
+    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description='金额校验失败')
34 57
 
35 58
 
36 59
 class PayStatusCode(BaseStatusCode):
@@ -39,39 +62,39 @@ class PayStatusCode(BaseStatusCode):
39 62
 
40 63
 class WithdrawStatusCode(BaseStatusCode):
41 64
     """ 4042xx 提现相关错误码 """
42
-    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description=u'提现金额不足')
65
+    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description='提现金额不足')
43 66
 
44 67
 
45 68
 class TokenStatusCode(BaseStatusCode):
46 69
     """ 4090xx 票据相关错误码 """
47
-    TOKEN_NOT_FOUND = StatusCodeField(409001, 'Token Not Found', description=u'票据不存在')
70
+    TOKEN_NOT_FOUND = StatusCodeField(409001, 'Token Not Found', description='票据不存在')
48 71
 
49 72
 
50 73
 class SignatureStatusCode(BaseStatusCode):
51 74
     """ 4091xx 签名校验错误 """
52
-    SIGNATURE_ERROR = StatusCodeField(409101, 'Signature Error', description=u'签名错误')
75
+    SIGNATURE_ERROR = StatusCodeField(409101, 'Signature Error', description='签名错误')
53 76
 
54 77
 
55 78
 class GVCodeStatusCode(BaseStatusCode):
56 79
     """ 4092xx 图形验证码相关错误码 """
57
-    GRAPHIC_VCODE_ERROR = StatusCodeField(409201, 'Graphic VCode Error', description=u'图形验证码错误')
80
+    GRAPHIC_VCODE_ERROR = StatusCodeField(409201, 'Graphic VCode Error', description='图形验证码错误')
58 81
 
59 82
 
60 83
 class SVCodeStatusCode(BaseStatusCode):
61 84
     """ 4093xx 短信验证码相关错误码 """
62
-    SMS_QUOTA_LIMIT = StatusCodeField(409300, 'SMS Quota Limit', description=u'短信次数超限')
63
-    SMS_VCODE_ERROR = StatusCodeField(409301, 'SMS VCode Error', description=u'验证码错误,请稍后重试')
64
-    SMS_VCODE_HAS_SEND = StatusCodeField(409302, 'SMS VCode Has Send', description=u'验证码已发送,请勿重复获取')
85
+    SMS_QUOTA_LIMIT = StatusCodeField(409300, 'SMS Quota Limit', description='短信次数超限')
86
+    SMS_VCODE_ERROR = StatusCodeField(409301, 'SMS VCode Error', description='验证码错误,请稍后重试')
87
+    SMS_VCODE_HAS_SEND = StatusCodeField(409302, 'SMS VCode Has Send', description='验证码已发送,请勿重复获取')
65 88
 
66 89
 
67 90
 class InsufficientStatusCode(BaseStatusCode):
68 91
     """ 4095xx 不足相关错误码 """
69
-    BALANCE_INSUFFICIENT = StatusCodeField(409501, 'Balance Insufficient', description=u'余额不足')
70
-    INTEGRAL_INSUFFICIENT = StatusCodeField(409502, 'Integral Insufficient', description=u'积分不足')
92
+    BALANCE_INSUFFICIENT = StatusCodeField(409501, 'Balance Insufficient', description='余额不足')
93
+    INTEGRAL_INSUFFICIENT = StatusCodeField(409502, 'Integral Insufficient', description='积分不足')
71 94
 
72 95
 
73 96
 class PermissionStatusCode(BaseStatusCode):
74 97
     """ 4099xx 权限相关错误码 """
75
-    PERMISSION_DENIED = StatusCodeField(409900, 'Permission Denied', description=u'权限不足')
76
-    UPLOAD_PERMISSION_DENIED = StatusCodeField(409910, 'Upload Permission Denied', description=u'上传权限不足')
77
-    UPDATE_PERMISSION_DENIED = StatusCodeField(409930, 'Update Permission Denied', description=u'更新权限不足')
98
+    PERMISSION_DENIED = StatusCodeField(409900, 'Permission Denied', description='权限不足')
99
+    UPLOAD_PERMISSION_DENIED = StatusCodeField(409910, 'Upload Permission Denied', description='上传权限不足')
100
+    UPDATE_PERMISSION_DENIED = StatusCodeField(409930, 'Update Permission Denied', description='更新权限不足')